/*
 * Università degli Studi di Trento
 * Facoltà di Scienze MM.FF.NN.
 * Corso di Laurea Triennale in Informatica
 * A.A. 2010/2011
 * Progetto per il corso di Sistemi Operativi 1
 * Progetto 4: simulazione di una rotonda
 * Autori:
 *  Abram Lorenzo - 140368
 *  Dal Lago Loris - 139428
 *  Florio Alessandro - 140245
 */

/********************************************************************************
  La classe interna Driver è privata.
********************************************************************************/

#include "driver.h"

static ptrBaut shared_baut = NULL; /* la rotatoria è globale su questo modulo, perché condivisa da tutti i conducenti */

typedef enum {
  IN_QUEUE, IN_BAUT, IN_OUTBOUND
} Status;

typedef struct SDriver {  
  Status status;
  ptrCar car;
} Driver;

typedef struct SDriver * ptrDriver; 

static void Driver_init(ptrDriver driver, ptrCar car, ptrBaut baut){
  driver->status = IN_QUEUE; /* l'auto inizia il suo esistere da quando si trova in coda */
  driver->car = car;
  if (shared_baut == NULL) shared_baut = baut;
}

static ptrDriver Driver_create(ptrCar car, ptrBaut baut){
  ptrDriver driver = new(Driver);
  Driver_init(driver, car, baut);
  return driver;
}

static void Driver_deinit(ptrDriver driver){
  Car_destroy(driver->car); /* Una macchina non puo muoversi senza guidatore */
}

static void Driver_destroy(ptrDriver driver){
  if (driver != NULL){
    Driver_deinit(driver);
    delete(driver);
  }
}

static int Driver_followingPos(int pos, int offset){
  return (pos+offset)%Baut_LEN; /* Posizione relativa  */
}


/* Questo metodo rappresenta il thread che gestira' i movimenti dell'auto */
void * Driver_drive(void * arg){
  struct toDrive_data * data = (struct toDrive_data *) arg;

#ifdef DEBUG
  printf("\t\t\t\t\tThread Creato, AUTO %s\n", Car_getTag(data->car));
#endif /* DEBUG  */

  ptrDriver driver = Driver_create(data->car, data->baut);
  /* Acquisisco i mutex dalla rotatoria per gestire accessi concorrenti */
  pthread_mutex_t * road = Baut_getMutexRoad(shared_baut);

  bool isTimeToExit = false;
  /* Finche' l'auto non si allontana dalla rotatoria */
  while(!isTimeToExit) {
    switch (driver->status) {
    case IN_QUEUE:
      {
	Place startPoint = Car_getStartPoint(driver->car);
	if (driver->car == Baut_getTopQueueCar(startPoint, shared_baut)){ /* Se non ho nessuno davanti */
	  pthread_mutex_lock(&road[3*startPoint]); /* Blocco il settore di strada in cui mi immettero' */
	  Car_move(driver->car);
	  Baut_notify(driver->car, shared_baut);
	  driver->status = IN_BAUT; /* Sono ora in rotatoria */
	}
      }
      break;
    case IN_BAUT:
      {
        int pos = Car_getPos(driver->car);
        if (Driver_followingPos(pos, 1) != Car_getDestPoint(driver->car)*3){ /* Se sono ancora lontano dalla mia uscita */
          pthread_mutex_lock(&road[Driver_followingPos(pos, 1)]); /* Blocco il settore in cui mi muovero' */
          /* Attendo il momento di muovermi dettato dalla mia velocità */
		  while ((long)time(NULL) < (long)Car_getSectArrivalTime(driver->car) + (long)Car_getSpeed(driver->car));
		  Car_move(driver->car);
		  Baut_notify(driver->car, shared_baut);
		  pthread_mutex_unlock(&road[pos]);	  /* Sblocco il settore da cui mi sono appena mosso */
        } else { /* Se sono giunto alla mia uscita */
        	/* Attendo il momento di muovermi dettato dalla mia velocità */
			  while ((long)time(NULL) < (long)Car_getSectArrivalTime(driver->car) + (long)Car_getSpeed(driver->car));
			  Car_move(driver->car);
			  Baut_notify(driver->car, shared_baut);
	  	  pthread_mutex_unlock(&road[pos]); /* Sblocco il settore da cui mi sono appena mosso */
	  	  driver->status = IN_OUTBOUND; /* Sto uscendo */
	}
      }    
      break;
    case IN_OUTBOUND:
      {
    	  /* Mi allontano dalla rotatoria */
    	  while ((long)time(NULL) < (long)Car_getSectArrivalTime(driver->car) + (long)Car_getSpeed(driver->car));
    	  Car_move(driver->car);
    	  Baut_notify(driver->car, shared_baut);
    	  isTimeToExit = true; /* Ho finito e mi sto allontanando dalla rotatoria */
      }      
      break;
    default:
      /* non viene mai raggiunto, nel caso dai errore */
      throw_error("Driver_drive", "driver->status non considerato");
    }
  }
  
  Driver_destroy(driver); /* ricordiamo che il driver distrugge anche la sua automobile  */
  pthread_exit(NULL);
}
